package com.husky.easyexcle.listener;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import com.husky.easyexcle.entity.ReadExcelBasicEntity;
import com.husky.easyexcle.service.ReadExcelBasicService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;

import java.util.List;

/**
 * @Author husky
 * @Date 2023/2/16 16:59
 * @Description: 基础读取Excel实体类对应监听器
 **/
@Slf4j
public class ReadExcelBasicListener implements ReadListener<ReadExcelBasicEntity> {
    /**
     * 每隔51条存储数据库，实际使用中可以100条，然后清理list ，方便内存回收
     */
    private static final int BATCH_COUNT = 51;
    /**
     * 缓存的数据
     */
    private List<ReadExcelBasicEntity> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
    /**
     * 业务接口。当然如果不用存储这个对象没用。
     */
    private ReadExcelBasicService readExcelBasicService;

    // public ReadExcelBasicListener() {
    //     // 这里是demo，所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
    //     readExcelBasicService = new ReadExcelBasicService();
    // }

    /**
     * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
     *
     * @param readExcelBasicService
     */
    public ReadExcelBasicListener(ReadExcelBasicService readExcelBasicService) {
        this.readExcelBasicService = readExcelBasicService;
    }

    /**
     * 这个每一条数据解析都会来调用
     * @param readExcelBasicEntity 最简单的 读取 Excel 对应实体类
     * @param analysisContext 获取Excel上下文对象
     */
    @Override
    public void invoke(ReadExcelBasicEntity readExcelBasicEntity, AnalysisContext analysisContext) {
        log.info("解析到一条数据:{}", JSON.toJSONString(readExcelBasicEntity));
        cachedDataList.add(readExcelBasicEntity);
        // 达到BATCH_COUNT了，需要去存储一次数据库，防止数据几万条数据在内存，容易OOM
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            // 存储完成清理 list
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    /**
     * 数据解析完成调用
     * @param analysisContext 获取Excel上下文对象
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {

        if (CollectionUtils.isNotEmpty(cachedDataList)) {
            // 这里也要保存数据，确保最后遗留的数据也存储到数据库
            saveData();
        }
        log.info("所有数据解析完成！");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据，开始存储数据库！", cachedDataList.size());
        readExcelBasicService.save(cachedDataList);
        log.info("存储数据库成功！");
    }
    /**
     * 当任何一个侦听器执行错误报告时，所有侦听器都会收到此方法。如果这里抛出异常，则整个读取操作将终止。
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     * @param exception 异常
     * @param analysisContext 获取Excel上下文对象
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext analysisContext) {
        log.error("解析失败，但是继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行，第{}列解析异常，数据为:{}", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }
    }
}
